#include "SmoothWindowZoomAnimation.h"

///Copyright (c) 2009-2010 T. Klepzig 

map<HWND, SmoothWindowZoomAnimation*> SmoothWindowZoomAnimation::instances;


SmoothWindowZoomAnimation::SmoothWindowZoomAnimation(HWND hWnd)
{
	this->hWnd=hWnd;
	instances[hWnd]=this;
	timerEnabled=false;

	onAnimationStartedPtr=0;
	onAnimationCancelledPtr=0;
	onAnimationFinishedPtr=0;
}

SmoothWindowZoomAnimation::~SmoothWindowZoomAnimation()
{
	StopTimer();
	instances.erase(hWnd);
}

HWND SmoothWindowZoomAnimation::GetHWnd()
{
	return hWnd;
}

bool SmoothWindowZoomAnimation::IsRunning()
{
	return timerEnabled;
}


bool SmoothWindowZoomAnimation::StartAnimation(int speed, int windowEdgeStartValue, int amount, WindowEdge windowEdge)
{
	if (!hWnd || speed < 1 || amount == 0 || timerEnabled)
		return false;

	RECT windowRect={0};
	GetWindowRect(hWnd, &windowRect);

	this->speed = speed;
	this->windowEdgeStartValue = windowEdgeStartValue;
	this->windowEdgeCurrentValue = windowEdgeStartValue;

	increment = amount > 0;
	this->amount = abs(amount);

	this->windowEdge = windowEdge;		
	this->startPoint.x = windowRect.left;
	this->startPoint.y = windowRect.top;


	if (windowEdge == WE_WIDTH)
		factor = (double)((double)RECTHEIGHT(windowRect) / (double)RECTWIDTH(windowRect));
	else if (windowEdge == WE_HEIGHT)
		factor = (double)((double)RECTWIDTH(windowRect) / (double)RECTHEIGHT(windowRect));

	if (increment)
		counter = 0;
	else
		counter = this->amount;

	StartTimer();
	CallWndZoomAnimationEvent(onAnimationStartedPtr, this, counter);

	return true;
}

bool SmoothWindowZoomAnimation::CancelAnimation()
{
	if (timerEnabled)
	{
		StopTimer();
		CallWndZoomAnimationEvent(onAnimationCancelledPtr, this, counter);
		return true;
	}

	return false;
}

void SmoothWindowZoomAnimation::AnimationStarted(WNDZOOMANIMATIONPROC ptr)
{
	this->onAnimationStartedPtr=ptr;
}

void SmoothWindowZoomAnimation::AnimationCancelled(WNDZOOMANIMATIONPROC ptr)
{
	this->onAnimationCancelledPtr=ptr;
}

void SmoothWindowZoomAnimation::AnimationFinished(WNDZOOMANIMATIONPROC ptr)
{
	this->onAnimationFinishedPtr=ptr;
}


void SmoothWindowZoomAnimation::StartTimer()
{
	if (!timerEnabled)
	{
		SetTimer(hWnd, 42, 10, TimerProc);
		timerEnabled=true;
	}
}

void SmoothWindowZoomAnimation::StopTimer()
{
	if (timerEnabled)
	{
		KillTimer(hWnd, 42);
		timerEnabled=false;
	}
}

int SmoothWindowZoomAnimation::Round(double x)
{
	x += 0.5;
	x = floor(x);
	return (int)x;
}

void SmoothWindowZoomAnimation::Animate()
{
	if (increment)
	{
		if (windowEdgeCurrentValue <= ((windowEdgeStartValue + amount) - speed))
		{
			windowEdgeCurrentValue += speed;

			if (windowEdge == WE_WIDTH)
			{
				RECT windowRect={0};
				GetWindowRect(hWnd, &windowRect);

				SetWindowPos(hWnd, 0,
					windowRect.left - speed, 
					windowRect.top - Round(speed * factor), 
					RECTWIDTH(windowRect) + speed * 2, 
					Round((RECTWIDTH(windowRect) + speed * 2) * factor),
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
			else if (windowEdge == WE_HEIGHT)
			{
				RECT windowRect={0};
				GetWindowRect(hWnd, &windowRect);

				SetWindowPos(hWnd, 0,
					windowRect.left - Round(speed * factor), 
					windowRect.top - speed, 
					Round((RECTHEIGHT(windowRect) + speed * 2) * factor), 
					RECTHEIGHT(windowRect) + speed * 2,
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
		}
		else
		{
			if (windowEdge == WE_WIDTH)
			{
				SetWindowPos(hWnd, 0,
					startPoint.x - amount, 
					startPoint.y - Round(amount * factor), 
					windowEdgeStartValue + amount * 2, 
					Round((windowEdgeStartValue + amount * 2) * factor),
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
			else if (windowEdge == WE_HEIGHT)
			{
				SetWindowPos(hWnd, 0,
					startPoint.x - Round(amount * factor), 
					startPoint.y - amount, 
					Round((windowEdgeStartValue + amount * 2) * factor), 
					windowEdgeStartValue + amount * 2,
					SWP_NOACTIVATE | SWP_NOZORDER);
			}

			StopTimer();
			CallWndZoomAnimationEvent(onAnimationFinishedPtr, this, counter);
		}

		counter += speed;
		if (counter > (amount - amount / (double)5) && speed > 2)
			speed /= 2;
	}
	else
	{
		if (windowEdgeCurrentValue >= ((windowEdgeStartValue - amount) + speed))
		{
			windowEdgeCurrentValue -= speed;

			if (windowEdge == WE_WIDTH)
			{
				RECT windowRect={0};
				GetWindowRect(hWnd, &windowRect);

				SetWindowPos(hWnd, 0,
					windowRect.left + speed, 
					windowRect.top + Round(speed * factor), 
					RECTWIDTH(windowRect) - speed * 2, 
					Round((RECTWIDTH(windowRect) - speed * 2) * factor),
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
			else if (windowEdge == WE_HEIGHT)
			{
				RECT windowRect={0};
				GetWindowRect(hWnd, &windowRect);

				SetWindowPos(hWnd, 0,
					windowRect.left + Round(speed * factor), 
					windowRect.top + speed, 
					Round((RECTHEIGHT(windowRect) - speed * 2) * factor), 
					RECTHEIGHT(windowRect) - speed * 2,
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
		}
		else
		{
			if (windowEdge == WE_WIDTH)
			{
				SetWindowPos(hWnd, 0,
					startPoint.x + amount, 
					startPoint.y + Round(amount * factor), 
					windowEdgeStartValue - amount * 2, 
					Round((windowEdgeStartValue - amount * 2) * factor),
					SWP_NOACTIVATE | SWP_NOZORDER);
			}
			else if (windowEdge == WE_HEIGHT)
			{
				SetWindowPos(hWnd, 0,
					startPoint.x + Round(amount * factor), 
					startPoint.y + amount, 
					Round((windowEdgeStartValue - amount * 2) * factor), 
					windowEdgeStartValue - amount * 2,
					SWP_NOACTIVATE | SWP_NOZORDER);
			}

			StopTimer();
			CallWndZoomAnimationEvent(onAnimationFinishedPtr, this, counter);
		}

		counter -= speed;
		if (counter < (amount / (double)5) && speed > 2)
			speed /= 2;
	}
}

void SmoothWindowZoomAnimation::CallWndZoomAnimationEvent(WNDZOOMANIMATIONPROC ptr, SmoothWindowZoomAnimation* sender, int currentValue)
{
	if (ptr)
		ptr(sender, currentValue);
}


VOID CALLBACK SmoothWindowZoomAnimation::TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	instances[hWnd]->Animate();
}
